#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sikle.h"

void SFile_push(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp;

  //  printf("SFile_push(base:%p,sp:%d)\n", es->base, es->sp);

  if(stacel->type == STACEL_FILE) {
    *(SFile*)sp = *(SFile*)stacel;
    sp += sizeof(SFile);
    STACEL(sp)->type = STACEL_FILE;
    es->sp += sizeof(SFile) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SFile_push error", NULL);
  }
}

void SFile_pop(SikleState* ss, Stacel* stacel)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_FILE) {
    sp -= sizeof(SFile);
    *(SFile*)stacel = *(SFile*)sp;
    es->sp = sp - es->base;
  }
  else {
    log_msg(LOG_ERROR, "SFile_pop error", NULL);
  }
}

void SFile_drop(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  
  if(STACEL(sp)->type == STACEL_FILE) {
    es->sp -= sizeof(SFile) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SFile_drop error", NULL);
  }
}

void SFile_dup(SikleState* ss)
{
  SFile file;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_FILE) {
    sp -= sizeof(SFile);
    file = *(SFile*)sp;

    sp = es->base + es->sp;
    *(SFile*)sp = file;
    sp += sizeof(SFile);
    STACEL(sp)->type = STACEL_FILE;
    es->sp += sizeof(SFile) + sizeof(Stacel);
  }
  else {
    log_msg(LOG_ERROR, "SFile_dup error", NULL);
  }
}

void SFile_p(SikleState* ss)
{
  SFile file;
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_FILE) {
    char buff[50];

    sp -= sizeof(SFile);
    file = *(SFile*)sp;
    
    sprintf(buff, "[FILE]%p", file.value);
    printf("%s",buff);
  }
  else {
    log_msg(LOG_ERROR, "SFile_p error", NULL);
  }
}

void SFile_open(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);
  char* base = ss->string_pool->base;

  if(STACEL(sp)->type == STACEL_STRING) {
    StacelType type;
    SPString filename;
    SPString mode;
    SString string0;
    SString string1;
    
    SFile file;
    char ch0;
    char mode_buff[5];

    type = stacel_types[STACEL_STRING];
    type.operation->pop(ss, STACEL(&string0));
    mode = string0.value;

    type_check(get_stacel_type(ss), STACEL_STRING);
    type.operation->pop(ss, STACEL(&string1));
    filename = string1.value;

    strncpy(mode_buff, base + mode.offset, mode.size);
    mode_buff[mode.size] = 0;

    ch0 = base[filename.offset+filename.size];

    base[filename.offset+filename.size] = 0;

    //    printf("filename: %s, mode: %s\n", base + filename.offset, mode_buff);

    file.type = STACEL_FILE;
    file.value = fopen(base + filename.offset, mode_buff);

    base[filename.offset+filename.size] = ch0;

    type = stacel_types[STACEL_FILE];
    type.operation->push(ss, STACEL(&file));
  }
  else {
    log_msg(LOG_ERROR, "SFile_open error", NULL);
  }
}

void SFile_close(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_FILE) {
    SFile file;
    SInteger integer;
    StacelType type;

    type = stacel_types[STACEL_FILE];

    type.operation->pop(ss, STACEL(&file));

    integer.type = STACEL_INTEGER;
    integer.value = fclose(file.value);
    
    type = stacel_types[STACEL_INTEGER];
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SFile_close error", NULL);
  }
}

void SFile_read(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    SFile file;
    SPointer ptr;
    SInteger size;
    SInteger nobj;
    SInteger integer;
    StacelType type;
    
    type = stacel_types[STACEL_INTEGER];
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    type.operation->pop(ss, STACEL(&nobj));

    type_check(get_stacel_type(ss), STACEL_INTEGER);
    type.operation->pop(ss, STACEL(&size));

    type = stacel_types[STACEL_POINTER];
    type_check(get_stacel_type(ss), STACEL_POINTER);
    type.operation->pop(ss, STACEL(&ptr));

    type = stacel_types[STACEL_FILE];
    type.operation->dup(ss);
    type.operation->pop(ss, STACEL(&file));

    integer.type = STACEL_INTEGER;
    integer.value = fread(ptr.value, size.value, nobj.value, file.value);
    
    type = stacel_types[STACEL_INTEGER];
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SFile_read error", NULL);
  }
}

void SFile_write(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    SFile file;
    SPointer ptr;
    SInteger size;
    SInteger nobj;
    SInteger integer;
    StacelType type;
    
    type = stacel_types[STACEL_INTEGER];
    type_check(get_stacel_type(ss), STACEL_INTEGER);
    type.operation->pop(ss, STACEL(&nobj));

    type_check(get_stacel_type(ss), STACEL_INTEGER);
    type.operation->pop(ss, STACEL(&size));

    type = stacel_types[STACEL_POINTER];
    type_check(get_stacel_type(ss), STACEL_POINTER);
    type.operation->pop(ss, STACEL(&ptr));

    type = stacel_types[STACEL_FILE];
    type.operation->dup(ss);
    type.operation->pop(ss, STACEL(&file));

    integer.type = STACEL_INTEGER;
    integer.value = fwrite(ptr.value, size.value, nobj.value, file.value);
    
    type = stacel_types[STACEL_INTEGER];
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SFile_write error", NULL);
  }
}

void SFile_flush(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_FILE) {
    SFile file;
    SInteger integer;
    StacelType type;

    type = stacel_types[STACEL_FILE];

    type.operation->pop(ss, STACEL(&file));

    integer.type = STACEL_INTEGER;
    integer.value = fflush(file.value);
    
    type = stacel_types[STACEL_INTEGER];
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SFile_flush error", NULL);
  }
}

void SFile_is_eof(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  if(STACEL(sp)->type == STACEL_INTEGER) {
    SInteger integer;
    StacelType type;

    type = stacel_types[STACEL_INTEGER];
    type.operation->pop(ss, STACEL(&integer));

    integer.type = STACEL_INTEGER;
    integer.value = integer.value == EOF;
    
    type = stacel_types[STACEL_INTEGER];
    type.operation->push(ss, STACEL(&integer));
  }
  else {
    log_msg(LOG_ERROR, "SFile_is_eof error", NULL);
  }
}

void SFile_register_builtin_words(SikleState* ss)
{
  register_builtin_word(ss, "fopen", SFile_open);
  register_builtin_word(ss, "fread", SFile_read);
  register_builtin_word(ss, "fwrite", SFile_write);
  register_builtin_word(ss, "fclose", SFile_close);
  register_builtin_word(ss, "fflush", SFile_flush);
  register_builtin_word(ss, "EOF?", SFile_is_eof);
}

StacelOperation file_ops = {
  .push = SFile_push,
  .pop = SFile_pop,
  .drop = SFile_drop,
  .dup = SFile_dup,

  .p = SFile_p,

  .register_builtin_words = SFile_register_builtin_words
};
